package com.lowca.news.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import com.lowca.news.dao.BaseDao;
import com.lowca.news.dao.support.Pager;
import com.lowca.news.util.Exceptions;

@Repository
public class BaseDaoImpl<T, PK extends Serializable> implements BaseDao<T, PK> {

	private Class<T> entityClass;
	@Resource
	private HibernateTemplate hibernateTemplate;

	protected HibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	@SuppressWarnings("unchecked")
	public BaseDaoImpl() {
		this.entityClass = null;
		Class<?> c = getClass();
		Type type = c.getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			Type[] parameterizedType = ((ParameterizedType) type)
					.getActualTypeArguments();
			this.entityClass = (Class<T>) parameterizedType[0];
		}
	}

	private String getPKName() {
		ClassMetadata meta = hibernateTemplate.getSessionFactory()
				.getClassMetadata(entityClass);
		String pkName = meta.getIdentifierPropertyName();
		return pkName;
	}

	@Override
	public T get(PK id) {
		Assert.notNull(id, "id is required");
		return (T) hibernateTemplate.get(entityClass, id);
	}

	@Override
	public T load(PK id) {
		Assert.notNull(id, "id is required");
		return (T) hibernateTemplate.load(entityClass, id);
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> getAll(PK[] ids) {
		Assert.notEmpty(ids, "ids must not be empty");
		String hql = "from " + entityClass.getName()
				+ " as model where model.id in(:ids)";
		return hibernateTemplate.findByNamedParam(hql, "ids", ids);
	}

	@Override
	public T findUniqueByProperty(String propertyName, Object value) {
		List<T> list = findByProperty(propertyName, value);
		Assert.isTrue(list.size() == 0 || list.size() == 1, "结果集异常");
		return (list.size() == 1) ? (T) (list.get(0)) : null;
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> findByProperty(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		String hql = "from " + entityClass.getName() + " as model where model."
				+ propertyName + " = ?";
		return hibernateTemplate.find(hql, value);
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		String hql = "from " + entityClass.getName();
		return hibernateTemplate.find(hql);
	}

	@Override
	public Long countAll() {
		String hql = "select count(model) from " + entityClass.getName()
				+ " model";
		return (Long) hibernateTemplate.find(hql).get(0);
	}

	@Override
	public boolean existByProperty(String propertyName, Object value) {
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		String hql = "select count(model) from " + entityClass.getName()
				+ " as model where model." + propertyName + " = ?";
		long totalCount = countByHQL(hql, value);
		return totalCount >= 1;
	}

	@Override
	@SuppressWarnings("unchecked")
	public PK save(T entity) {
		Assert.notNull(entity, "entity is required");
		return (PK) hibernateTemplate.save(entity);
	}

	@Override
	public void update(T entity) {
		Assert.notNull(entity, "entity is required");
		hibernateTemplate.update(entity);
	}

	@Override
	public void saveOrUpdate(T entity) {
		Assert.notNull(entity, "entity is required");
		hibernateTemplate.saveOrUpdate(entity);
	}

	@Override
	public void delete(T entity) {
		Assert.notNull(entity, "entity is required");
		hibernateTemplate.delete(entity);
	}

	@Override
	public void delete(PK id) {
		Assert.notNull(id, "id is required");
		T entity = load(id);
		hibernateTemplate.delete(entity);
	}

	@Override
	public int deleteAll(final PK[] ids) {
		Assert.notEmpty(ids, "ids must not be empty");
		final String hql = "delete from " + entityClass + " model where model."
				+ getPKName() + " in(:id)";
		return hibernateTemplate.execute(new HibernateCallback<Integer>() {
			public Integer doInHibernate(Session session)
					throws HibernateException, SQLException {
				Query query = session.createQuery(hql);
				query.setParameterList("ids", ids);
				return query.executeUpdate();
			}
		});
	}

	@Override
	public void deleteAll(Collection<T> entities) {
		Assert.notEmpty(entities, "entities must not be empty");
		hibernateTemplate.deleteAll(entities);
	}

	@Override
	public void flush() {
		hibernateTemplate.flush();
	}

	@Override
	public void clear() {
		hibernateTemplate.clear();
	}

	@Override
	public void evict(Object object) {
		Assert.notNull(object, "object is required");
		hibernateTemplate.evict(object);
	}

	protected long countByCriteria(Criteria criteria) {
		Assert.notNull(criteria, "criteria is required");
		Field field = null;
		List orderEntrys = null;
		CriteriaImpl impl = (CriteriaImpl) criteria;
		try {
			field = CriteriaImpl.class.getDeclaredField("orderEntries");
			// Get orders
			orderEntrys = (List) field.get(impl);
			// Remove orders
			field.set(criteria, new ArrayList(0));
		} catch (Exception e) {
			Exceptions.unchecked(e);
		}
		// 执行Count查询
		criteria.setProjection(Projections.rowCount());
		long totalCount = (Long) criteria.uniqueResult();
		// 恢复场景
		criteria.setProjection(null);
		List innerOrderEntries = null;
		try {
			innerOrderEntries = (List) field.get(criteria);
			for (int i = 0; i < orderEntrys.size(); i++) {
				innerOrderEntries.add(orderEntrys.get(i));
			}
		} catch (Exception e) {
			Exceptions.unchecked(e);
		}
		return totalCount;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Pager<T> findPageByCriteria(final int pageNo, final int pageSize,
			final DetachedCriteria detachedCriteria) {
		Assert.notNull(detachedCriteria, "detachedCriteria is required");
		return (Pager<T>) hibernateTemplate
				.execute(new HibernateCallback<Pager<T>>() {

					@Override
					public Pager<T> doInHibernate(Session session)
							throws HibernateException, SQLException {
						Criteria criteria = detachedCriteria
								.getExecutableCriteria(session);
						long totalCount = countByCriteria(criteria);
						criteria.setFirstResult((pageNo - 1) * pageSize);
						criteria.setMaxResults(pageSize);
						// 设置好分页对象
						Pager<T> pager = new Pager<T>();
						pager.setTotalCount(totalCount);
						pager.setList(criteria.list());
						return pager;
					}

				});
	}

	@Override
	@SuppressWarnings("unchecked")
	public T findUniqueByHQL(String hql, Object... params) {
		Assert.hasText(hql, "hql is required");
		return (T) hibernateTemplate.find(hql, params);
	}

	@Override
	public long countByHQL(String hql, Object... params) {
		Assert.hasText(hql, "hql is required");
		String fromHql = hql;
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");
		String countHql = "select count(*) " + fromHql;
		List list = hibernateTemplate.find(countHql, params);
		return (Long) list.get(0);
	}

	@Override
	@SuppressWarnings("unchecked")
	public Pager<T> findPageByHQL(final int pageNo, final int pageSize,
			final String hql, final Object... params) {
		Assert.hasText(hql, "hql is required");
		return (Pager<T>) hibernateTemplate
				.execute(new HibernateCallback<Pager<T>>() {

					@Override
					public Pager<T> doInHibernate(Session session)
							throws HibernateException, SQLException {
						long totalCount = countByHQL(hql, params);
						Query query = session.createQuery(hql);
						query.setFirstResult((pageNo - 1) * pageSize);
						query.setMaxResults(pageSize);
						if (params != null) {
							for (int i = 0; i < params.length; i++) {
								query.setParameter(i, params[i]);
							}
						}
						// 设置好分页对象
						Pager<T> pager = new Pager<T>();
						pager.setTotalCount(totalCount);
						pager.setList(query.list());
						return pager;
					}

				});

	}

	protected Session getSession() {
		return getHibernateTemplate().getSessionFactory().getCurrentSession();
	}

}